package cn.chmcyz.bootredis.filter;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;

@Component
//@ConfigurationProperties(prefix = "bloom.filter")
public class RedisBloomFilter {
    @Value("${bloom.filter.expectInsertion}")
    private long expectInsertion;
    @Value("${bloom.filter.fpp}")
    private Double fpp;
    private long numBits;
    private int hashNums;
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;
    public static List<Long> list = new ArrayList<>();

    @PostConstruct
    public void init(){
        this.optimalNumOfBits(expectInsertion,fpp);
        this.optimalNumOfHashFunctions(expectInsertion,numBits);
    }

    /**
     * 计算位数组长度
     * @param n 期望插入值长度
     * @param p 误判率
     */
    private void optimalNumOfBits(long n, double p) {
        if (p == 0.0D) {
            p = 4.9E-324D;
        }

        numBits = (long) ((double) (-n) * Math.log(p) / (Math.log(2.0D) * Math.log(2.0D)));
    }

    /**
     * hash函数个数
     *
     * @param n 预计插入量
     * @param m 位数组长度
     * @return
     */
    private void optimalNumOfHashFunctions(long n, long m) {
        hashNums = Math.max(1, (int) Math.round((double) m / (double) n * Math.log(2.0D)));
    }

    /**
     * 往布隆过滤器添加某个key
     * @param key
     */
    public void put(String key) {
        long[] result = getIndex(key);
        for (long l : result) {
            redisTemplate.opsForValue().setBit("chuantou", l, true);
        }
    }

    /**
     * 某个key是否在布隆过滤器中存在
     * @param key
     * @return
     */
    public boolean exists(String key) {
        long[] result = getIndex(key);
        for (long l : result) {
            Boolean exist = redisTemplate.opsForValue().getBit("chuantou", l);
            if (Boolean.FALSE.equals(exist)) {
                return false;
            }
        }
        return true;
    }

    /**
     * hash方法
     * @param key
     * @return
     */
    private long hash(String key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode());
    }

    private long[] getIndex(String key) {
        long[] result = new long[hashNums];
        long hash1 = this.hash(key);
        long hash2 = hash1 >>> 16;
        //对key进行多次hash
        for (int i = 0; i < hashNums; i++) {
            long hash = hash1 + i * hash2;
            hash = hash % numBits;
            if (hash < 0) {
                hash = ~hashNums;
            }
            result[i] = hash;
            list.add(hash);
        }

        return result;

    }
}
